/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package me.zhengjie.modules.system.rest;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.annotation.Log;
import me.zhengjie.annotation.rest.AnonymousPostMapping;
import me.zhengjie.config.RsaProperties;
import me.zhengjie.config.SmsUtils;
import me.zhengjie.modules.anniversary.domain.Student;
import me.zhengjie.modules.anniversary.domain.Teacher;
import me.zhengjie.modules.anniversary.service.StudentService;
import me.zhengjie.modules.anniversary.service.TeacherService;
import me.zhengjie.modules.anniversary.service.dto.StudentDto;
import me.zhengjie.modules.anniversary.service.dto.TeacherDto;
import me.zhengjie.modules.anniversary.service.mapstruct.StudentMapper;
import me.zhengjie.modules.anniversary.service.mapstruct.TeacherMapper;
import me.zhengjie.modules.security.config.bean.LoginProperties;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.modules.security.security.TokenProvider;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.modules.security.service.dto.JwtUserDto;
import me.zhengjie.modules.system.domain.Dept;
import me.zhengjie.modules.system.repository.UserRepository;
import me.zhengjie.modules.system.service.DataService;
import me.zhengjie.modules.system.domain.User;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.system.domain.vo.UserPassVo;
import me.zhengjie.modules.system.service.DeptService;
import me.zhengjie.modules.system.service.RoleService;
import me.zhengjie.modules.system.service.dto.RoleSmallDto;
import me.zhengjie.modules.system.service.dto.UserDto;
import me.zhengjie.modules.system.service.dto.UserQueryCriteria;
import me.zhengjie.modules.system.service.VerifyService;
import me.zhengjie.utils.*;
import me.zhengjie.modules.system.service.UserService;
import me.zhengjie.utils.enums.CodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static me.zhengjie.config.Constant.*;
import static me.zhengjie.config.SmsUtils.generateCode;


/**
 * @author Zheng Jie
 * @date 2018-11-23
 */
@Api(tags = "系统：用户管理")
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

    private final PasswordEncoder passwordEncoder;
    private final UserService userService;
    private final DataService dataService;
    private final DeptService deptService;
    private final RoleService roleService;
    private final VerifyService verificationCodeService;
    private final StudentService studentService;
    private final TeacherService teacherService;
    private final StudentMapper studentMapper;
    private final TeacherMapper teacherMapper;
    private final UserRepository userRepository;

    @Resource
    private LoginProperties loginProperties;
    private final SecurityProperties properties;
    private final OnlineUserService onlineUserService;
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    @Autowired
    private SmsUtils smsUtils;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Log("导出用户数据")
    @ApiOperation("导出用户数据")
    @GetMapping(value = "/download")
    @PreAuthorize("@el.check('user:list')")
    public void download(HttpServletResponse response, UserQueryCriteria criteria) throws IOException {
        userService.download(userService.queryAll(criteria), response);
    }

    @Log("查询用户")
    @ApiOperation("查询用户")
    @GetMapping
    @PreAuthorize("@el.check('user:list')")
    public ResponseEntity<Object> query(UserQueryCriteria criteria, Pageable pageable) {
        if (!ObjectUtils.isEmpty(criteria.getDeptId())) {
            criteria.getDeptIds().add(criteria.getDeptId());
            criteria.getDeptIds().addAll(deptService.getDeptChildren(criteria.getDeptId(),
                    deptService.findByPid(criteria.getDeptId())));
        }

        // 数据权限
        List<Long> dataScopes = dataService.getDeptIds(userService.findByName(SecurityUtils.getCurrentUsername()));
        // criteria.getDeptIds() 不为空并且数据权限不为空则取交集
        if (!CollectionUtils.isEmpty(criteria.getDeptIds()) && !CollectionUtils.isEmpty(dataScopes)) {
            // 取交集
            criteria.getDeptIds().retainAll(dataScopes);
            if (!CollectionUtil.isEmpty(criteria.getDeptIds())) {
                return new ResponseEntity<>(userService.queryAll(criteria, pageable), HttpStatus.OK);
            }
        } else {
            // 否则取并集
            criteria.getDeptIds().addAll(dataScopes);
            ResponseEntity<Object> results = new ResponseEntity<>(userService.queryAll(criteria, pageable), HttpStatus.OK);
            return results;
        }
        return new ResponseEntity<>(PageUtil.toPage(null, 0), HttpStatus.OK);
    }

    @Log("新增用户")
    @ApiOperation("新增用户")
    @PostMapping
    @PreAuthorize("@el.check('user:add')")
    public ResponseEntity<Object> create(@Validated @RequestBody User resources) {
        checkLevel(resources);
        // 默认密码 123456
        resources.setPassword(passwordEncoder.encode("123456"));
        userService.create(resources);
        return new ResponseEntity<>(HttpStatus.CREATED);
    }

    @Log("修改用户")
    @ApiOperation("修改用户")
    @PutMapping
    @PreAuthorize("@el.check('user:edit')")
    public ResponseEntity<Object> update(@Validated(User.Update.class) @RequestBody User resources) {
        checkLevel(resources);

        Boolean isVerifyChange = false;
        //若原认证状态不是 0 且认证状态有更改，则需要更新认证时间
        UserDto user = userService.findById(resources.getId());
        if(user != null && user.getVerifyStatus() != 0 && user.getVerifyStatus() != resources.getVerifyStatus()){
            isVerifyChange = true;
            resources.setVerifyTime(new Timestamp(System.currentTimeMillis()));
        }

        userService.update(resources);

        // 根据用户id查询是否有相应的学生信息
        Student student = studentMapper.toEntity(studentService.findByUserId(resources.getId()));
        if(student != null){ // 若存在相应的学生，则更新学生的认证状态
            student.setVerifyStatus(resources.getVerifyStatus());
            if(isVerifyChange)
                student.setVerifyTime(resources.getVerifyTime());
            studentService.update(student);
        }
        else{ // 否则查询是否有相应的教师信息
            Teacher teacher = teacherMapper.toEntity(teacherService.findByUserId(resources.getId()));
            if(teacher != null){ // 若存在相应的教师，则更新教师的认证状态
                teacher.setVerifyStatus(resources.getVerifyStatus());
                if(isVerifyChange)
                    teacher.setVerifyTime(resources.getVerifyTime());
                teacherService.update(teacher);
            }
        }
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @Log("修改用户：个人中心")
    @ApiOperation("修改用户：个人中心")
    @PutMapping(value = "center")
    public ResponseEntity<Object> center(@Validated(User.Update.class) @RequestBody User resources) {
        if (!resources.getId().equals(SecurityUtils.getCurrentUserId())) {
            throw new BadRequestException("不能修改他人资料");
        }
        userService.updateCenter(resources);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @Log("删除用户")
    @ApiOperation("删除用户")
    @DeleteMapping
    @PreAuthorize("@el.check('user:del')")
    public ResponseEntity<Object> delete(@RequestBody Set<Long> ids) {
        for (Long id : ids) {
            Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));
            Integer optLevel = Collections.min(roleService.findByUsersId(id).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));
            if (currentLevel > optLevel) {
                throw new BadRequestException("角色权限不足，不能删除：" + userService.findById(id).getUsername());
            }
        }
        userService.delete(ids);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @ApiOperation("修改密码")
    @PostMapping(value = "/updatePass")
    public ResponseEntity<Object> updatePass(@RequestBody UserPassVo passVo) throws Exception {
        String oldPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, passVo.getOldPass());
        String newPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, passVo.getNewPass());
        UserDto user = userService.findByName(SecurityUtils.getCurrentUsername());
        if (!passwordEncoder.matches(oldPass, user.getPassword())) {
            throw new BadRequestException("修改失败，旧密码错误");
        }
        if (passwordEncoder.matches(newPass, user.getPassword())) {
            throw new BadRequestException("新密码不能与旧密码相同");
        }
        userService.updatePass(user.getUsername(), passwordEncoder.encode(newPass));
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @ApiOperation("修改头像")
    @PostMapping(value = "/updateAvatar")
    public ResponseEntity<Object> updateAvatar(@RequestParam MultipartFile avatar) {
        return new ResponseEntity<>(userService.updateAvatar(avatar), HttpStatus.OK);
    }

    @Log("修改邮箱")
    @ApiOperation("修改邮箱")
    @PostMapping(value = "/updateEmail/{code}")
    public ResponseEntity<Object> updateEmail(@PathVariable String code, @RequestBody User user) throws Exception {
        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, user.getPassword());
        UserDto userDto = userService.findByName(SecurityUtils.getCurrentUsername());
        if (!passwordEncoder.matches(password, userDto.getPassword())) {
            throw new BadRequestException("密码错误");
        }
        verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + user.getEmail(), code);
        userService.updateEmail(userDto.getUsername(), user.getEmail());
        return new ResponseEntity<>(HttpStatus.OK);
    }

    /**
     * 如果当前用户的角色级别低于创建用户的角色级别，则抛出权限不足的错误
     *
     * @param resources /
     */
    private void checkLevel(User resources) {
        Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));
        Integer optLevel = roleService.findByRoles(resources.getRoles());
        if (currentLevel > optLevel) {
            throw new BadRequestException("角色权限不足");
        }
    }

    @Log("获取Openid")
    @ApiOperation("获取Openid")
    @AnonymousPostMapping(value = "/getOpenid")
    public String getOpenid(String code) throws Exception {
        System.out.println("+++code: " + code);
        //设置openid
        String openid = getOpenidStr(code, APP_ID, APP_SECRET);
        //判断openid不为空：isBlank是在isEmpty的基础上进行了为空（字符串都为空格、制表符、tab 的情况）的判断
        if (!StringUtils.isBlank(openid)) {
            //对openid字符串进行替换"\"字符替换为空
            openid = openid.replace("\"", "").trim();
        }
        System.out.println("openid:" + openid);
        return openid;
    }

    @Log("微信提交材料")
    @ApiOperation("微信提交材料")
    @RequestMapping(value = "/submitMaterial")
    @AnonymousAccess
    public ResponseEntity<Object> submitMaterial(@RequestParam Map<String,String> map){
        String type = map.get("type");
        String wxOpenId = map.get("wxOpenId");
        String enterDate = map.get("enterDate");
        String collegeid = map.get("collegeid");
        String majorDepartid = map.get("majorDepartid");
        String certificatePhotoPath = map.get("certificatePhotoPath");
        String brief = map.get("brief");
        certificatePhotoPath = certificatePhotoPath.replace(",",";");
        User user = userService.findByOpenid(wxOpenId);
        user.setEnterDate(enterDate);
//        user.setDeptId(Long.parseLong(majorDepartid));
//        user.setDeptId(Long.valueOf(collegeid));
        user.setCertificatePhotoPath(certificatePhotoPath);
        if (type.equals("1")){
            Long majorid = Long.valueOf(majorDepartid);
            user.setMajorDepartId(majorid);

            Student student = new Student();
            student.setUserId(user.getId());
            student.setName(user.getUsername());
            student.setMajorDepartId(majorid);
            student.setEnterDate(enterDate);
            student.setAcademyDepartId(Long.valueOf(collegeid));
            student.setPhone(user.getPhone());
            student.setVerifyStatus(0);
            student.setBrief(brief);
            student.setIsEnable(true);
            studentService.create(student);
        }else{
            Teacher teacher = new Teacher();
            teacher.setUserId(user.getId());
            teacher.setName(user.getUsername());
            teacher.setWorkDate(enterDate);
            teacher.setPhone(user.getPhone());
            teacher.setVerifyStatus(0);
            teacher.setBrief(brief);
            teacher.setIsEnable(true);
            teacherService.create(teacher);
        }
        userService.update(user);
        return ResponseEntity.ok(HttpStatus.OK);
    }


    private String getTokenByUser(User user, HttpServletRequest request){
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(user.getUsername(), user.getWxOpenId().substring(0, 6));
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 生成令牌
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token, request);
        return properties.getTokenStartWith() + token;
    }

    @Log("微信登录code")
    @ApiOperation("微信登录code")
    @AnonymousPostMapping(value = "/wxloginByCode")
    public ResponseEntity<Object> wxloginByCode(@RequestBody User user, HttpServletRequest request){
        Map<String, Object> authInfo = new HashMap<>(2);
        System.out.println("+++code: " + user.getCode());
        //设置openid
        String openid = getOpenidStr(user.getCode(), APP_ID, APP_SECRET);
        if (!StringUtils.isBlank(openid)) {
            openid = openid.replace("\"", "").trim();
        }
        user.setWxOpenId(openid);
        User sel_user = userService.findByOpenid(openid);
        String token = null;
        if (sel_user == null) {
            authInfo.put("token", 1);
            authInfo.put("user", user);
        } else {
            if (sel_user.getVerifyStatus() == 1) {
                authInfo.put("token",getTokenByUser(sel_user,request));
            }else{
                authInfo.put("token", 2);
            }
            authInfo.put("user", sel_user);
        }
        if (loginProperties.isSingleLogin()) {
            onlineUserService.checkLoginOnUser(user.getUsername(), token);
        }
        return ResponseEntity.ok(authInfo);
    }

    @Log("通过姓名和手机查找用户")
    @ApiOperation("通过姓名和手机查找用户")
    @AnonymousPostMapping(value = "/wxloginByNameAndPhone")
    public ResponseEntity<Object> wxloginByNameAndPhone(@RequestBody User user, HttpServletRequest request){
        Map<String, Object> authInfo = new HashMap<String, Object>(2);
        user.setPassword(passwordEncoder.encode(user.getWxOpenId().substring(0, 6)));
        user.setIsAdmin(false);
        user.setEnabled(true);
        user.setEmail(user.getPhone() + "@139.com");
        if(user.getWxNickName().equals("1")){
            Student student = studentService.findByNameAndPhone(user.getUsername(),user.getPhone());
            if(student != null){
                user.setVerifyStatus(1);
                user.setDeptId(student.getAcademyDepartment().getId());
                user.setMajorDepartId(student.getMajorDepartId());
            }else{
                user.setVerifyStatus(0);
            }
            UserDto userDto = userService.create(user);
            if (student != null){
                student.setUserId(userDto.getId());
                student.setVerifyStatus(1);
                studentService.update(student);
            }
        }else{
            Teacher teacher = teacherService.findByNameAndPhone(user.getUsername(),user.getPhone());
            if(teacher != null){
                user.setVerifyStatus(1);
                user.setEmail(teacher.getPhone() + "@139.com");
                user.setDept(teacher.getDepartment());
                user.setDeptId(teacher.getDepartment().getId());
            }else{
                user.setVerifyStatus(0);
            }
            UserDto userDto = userService.create(user);
            if (teacher != null){
                teacher.setUserId(userDto.getId());
                teacher.setVerifyStatus(1);
                teacherService.update(teacher);
            }
        }
        if (user.getVerifyStatus() == 1){
            authInfo.put("token",getTokenByUser(user,request));
        }else{
            authInfo.put("token", 2);
        }
        authInfo.put("user", user);
        return ResponseEntity.ok(authInfo);
    }

    @Log("获取用户验证码")
    @ApiOperation("获取用户验证码")
    @AnonymousPostMapping(value = "/getVerifyCode")
    public ResponseEntity<Object> getVerifyCode(@RequestBody JSONObject parm) {
        String phone = parm.get("phone").toString();
        System.out.println("phone:" + phone);
        // 生成验证码
        String code = generateCode(6);
        Map<String, Object> map = new HashMap<>(2);
        try {
            // 发送消息
            SendSmsResponse resp = smsUtils.sendSms(phone, code, APPNAME, VERIFYCODETEMPLATE);
            // 将code存入redis
            redisTemplate.opsForValue().set(KEY_PREFIX + phone, code, 5, TimeUnit.MINUTES);
            map.put("resp", resp);
            map.put("code", code);
            System.out.println("resp:" + resp);
            System.out.println("code:" + code);
        } catch (Exception e) {
            throw new BadRequestException("发送短信失败");
        }
        return ResponseEntity.ok(map);
    }

    @Log("新增微信用户")
    @ApiOperation("新增微信用户")
    @AnonymousPostMapping(value = "/newwxuser")
    public ResponseEntity<Object> newwxuser(@Validated @RequestBody User user) {
        String passwd = user.getWxOpenId().substring(0, 6);
        user.setPassword(passwordEncoder.encode(passwd));
        user.setEnabled(false);
        userService.create(user);
        return new ResponseEntity<>(HttpStatus.CREATED);
    }

    @Log("查询最新认证用户")
    @ApiOperation("查询最新认证用户")
    @GetMapping("/findByNewUsers")
    @AnonymousAccess
    public ResponseEntity<Object> findByNewUsers(Integer page, Integer size) {
        List<Map<String, Object>> users = userService.findByNewUsers(PageRequest.of(page, size));
        System.out.println(users);
        return new ResponseEntity<>(users, HttpStatus.OK);
    }

    @Log("查询个人信息")
    @ApiOperation("查询个人信息")
    @AnonymousPostMapping(value = "/getInformation")
    public ResponseEntity<Object> getInformation(String openid, String type){
        Map<String,Object> map;
        if (type.equals("1"))
            map = userService.getStuInformation(openid);
        else
            map = userService.getTeaInformation(openid);

        return new ResponseEntity<>(map, HttpStatus.OK);
    }

    @Log("设置个人信息")
    @ApiOperation("设置个人信息")
    @AnonymousPostMapping(value = "/setInformation")
    public ResponseEntity<Object> setInformation(@RequestParam Map<String,String> map){
        String userId = map.get("userId");
        String type = map.get("type");
        String birth_date = map.get("birth_date");
        String id_card_no = map.get("id_card_no");
        String postitional = map.get("postitional");
        String industry = map.get("industry");
        String job = map.get("job");
        String brief = map.get("brief");
        if (type.equals("1")){
            Student student = studentMapper.toEntity(studentService.findByUserId(Long.valueOf(userId)));
            student.setBirthDate(birth_date);
            student.setIdCardNo(id_card_no);
            student.setPostitional(postitional);
            student.setIndustry(industry);
            student.setJob(job);
            student.setBrief(brief);
            studentService.update(student);
        }else {
            Teacher teacher = teacherMapper.toEntity(teacherService.findByUserId(Long.valueOf(userId)));
            teacher.setUserId(Long.valueOf(userId));
            teacher.setBirthDate(birth_date);
            teacher.setIdCardNo(id_card_no);
            teacher.setPostitional(postitional);
            teacher.setIndustry(industry);
            teacher.setJob(job);
            teacher.setBrief(brief);
            teacherService.update(teacher);
        }
        return ResponseEntity.ok(HttpStatus.OK);
    }
}
